package com.qkun.library.utils;

import android.content.res.XmlResourceParser;
import android.text.TextUtils;
import android.util.Xml;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.XmlRes;
import androidx.core.util.Pair;

import com.qkun.library.base.BaseApplication;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author qinkun
 * @date 7/25 0025 19:47
 * @description
 */
public final class XmlTagParser {

    private XmlPullParser mXmlPullParser;
    private FilterRule[] mFilterRules;
    private ArraysEx.Comparator<FilterRule, String> TAG_NAME_COMPARATOR = new ArraysEx.Comparator<FilterRule, String>() {
        @Override
        public int compare(FilterRule o, String value) {
            return o.tagName.compareTo(value);
        }
    };

    private XmlTagParser() {
    }

    public static XmlTagParser make(@XmlRes int resId) {
        return new XmlTagParser().load(resId);
    }

    public static XmlTagParser make(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
        return new XmlTagParser().load(inputStream, inputEncoding);
    }

    public static XmlTagParser make(InputStream inputStream) throws XmlPullParserException {
        return make(inputStream, "UTF-8");
    }

    private XmlTagParser load(@XmlRes int resId) {
        mXmlPullParser = BaseApplication.getAppContext().getResources().getXml(resId);
        return this;
    }

    private XmlTagParser load(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
        mXmlPullParser = Xml.newPullParser();
        mXmlPullParser.setInput(inputStream, inputEncoding);
        return this;
    }

    public XmlTagParser addFilterRule(FilterRule filterRule) {
        mFilterRules = ArraysEx.merge(mFilterRules, filterRule);
        return this;
    }

    private List<Tag> parsingInternal(int threshold, OnSyncCallback onSyncCallback) {
        List<Tag> tagList = new ArrayList<>();
        if (Utils.nonNull(mXmlPullParser)) {
            LinkedList<Tag> tagStackList = new LinkedList<>();
            threshold = Math.max(1, threshold);
            int lastLength = 0;
            try {
                //先获取当前解析器光标在哪，如果没到文档的结束标志，则将当前解析器光标往下移继续去解析
                for (int event = mXmlPullParser.getEventType(); event != XmlPullParser.END_DOCUMENT; event = mXmlPullParser.next()) {
                    switch (event) {
                        case XmlPullParser.START_DOCUMENT://文档的开始标志
                            break;
                        case XmlPullParser.START_TAG://标签开始
                            //一般都是获取标签的属性值
                            String tagName = mXmlPullParser.getName();
                            boolean noFilter = ArraysEx.isEmpty(mFilterRules);
                            int pos = -1;
                            Tag tag = null;
                            if (noFilter || ArraysEx.isIncludeIndex(mFilterRules, pos = ArraysEx.binarySearch(mFilterRules, tagName, TAG_NAME_COMPARATOR))) {
                                tag = new Tag(tagName);
                                if (noFilter || (ArraysEx.isEmpty(mFilterRules[pos].attrs) && ArraysEx.isEmpty(mFilterRules[pos].attrsIndex))) {
                                    final int N = mXmlPullParser.getAttributeCount();
                                    for (int i = 0; i < N; i++) {
                                        String attrNamespace = mXmlPullParser.getAttributeNamespace(i);
                                        String attrName = mXmlPullParser.getAttributeName(i);
                                        String attrValue = mXmlPullParser.getAttributeValue(i);
                                        tag.attrs.add(Attribute.create(attrNamespace, attrName, i, attrValue));
                                    }
                                } else if (!ArraysEx.isEmpty(mFilterRules[pos].attrs)) {
                                    for (Attribute attr : mFilterRules[pos].attrs) {
                                        String attrValue = mXmlPullParser.getAttributeValue(attr.first, attr.second);
                                        tag.attrs.add(Attribute.create(attr.first, attr.second, attrValue));
                                    }
                                } else if (!ArraysEx.isEmpty(mFilterRules[pos].attrsIndex)) {
                                    for (int index : mFilterRules[pos].attrsIndex) {
                                        if (ArraysEx.isIncludeIndex(mXmlPullParser.getAttributeCount(), index)) {
                                            String attrNamespace = mXmlPullParser.getAttributeNamespace(index);
                                            String attrName = mXmlPullParser.getAttributeName(index);
                                            String attrValue = mXmlPullParser.getAttributeValue(index);
                                            tag.attrs.add(Attribute.create(attrNamespace, attrName, index, attrValue));
                                        }
                                    }
                                }
                            }
                            tagStackList.push(tag);
                            break;
                        case XmlPullParser.TEXT://标签内包裹的文本
                            tag = tagStackList.getFirst();
                            if (Utils.nonNull(tag)) {
                                tag.texts.add(mXmlPullParser.getText());
                            }
                            break;
                        case XmlPullParser.END_TAG://标签结束
                            tag = tagStackList.pop();
                            if (Utils.nonNull(tag)) {
                                tagList.add(tag);
                            }
                            if (Utils.nonNull(onSyncCallback) && ((tagList.size() - lastLength) >= threshold)) {
                                onSyncCallback.onSync(tagList, lastLength);
                                lastLength = tagList.size();
                            }
                        default:
                            break;
                    }
                }
                if (Utils.nonNull(onSyncCallback) && (tagList.size() != lastLength)) {
                    onSyncCallback.onSync(tagList, lastLength);
                }
            } catch (XmlPullParserException | IOException e) {
                MyLog.e(e);
            } finally {
                if (mXmlPullParser instanceof XmlResourceParser) {
                    ((XmlResourceParser) mXmlPullParser).close();
                }
            }
        }
        return tagList;
    }

    public List<Tag> parsing() {
        return parsingInternal(Integer.MAX_VALUE, null);
    }

    /**
     * 异步解析
     *
     * @param threshold      阈值，在大于或等于阈值的情况下会去同步
     * @param onSyncCallback
     */
    public void parsingSync(int threshold, OnSyncCallback onSyncCallback) {
        if (Utils.isNull(onSyncCallback)) {
            throw new IllegalArgumentException("This 'onSyncCallback' cannot be null!");
        }
        parsingInternal(threshold, onSyncCallback);
    }

    public interface OnSyncCallback {
        void onSync(List<Tag> tagList, int lastLength);
    }

    public static class FilterRule implements Comparable<FilterRule> {
        private String tagName;
        private Attribute[] attrs;
        private int[] attrsIndex;

        private FilterRule(String tagName) {
            if (TextUtils.isEmpty(tagName)) {
                throw new IllegalArgumentException("This 'tagName' cannot be empty!");
            }
            this.tagName = tagName;
        }

        public static FilterRule create(@NonNull String tagName) {
            return new FilterRule(tagName);
        }

        /**
         * 该 index对应的并不是Xml文件中属性的顺序，而是编码上的排序
         *
         * @param index
         * @return
         */
        public FilterRule filterAttr(int index) {
            attrsIndex = ArraysEx.merge(attrsIndex, index);
            return this;
        }

        public FilterRule filterAttr(String namespace, String name) {
            if (TextUtils.isEmpty(name)) {
                throw new IllegalArgumentException("This 'name' cannot be empty!");
            }
            attrs = ArraysEx.merge(attrs, Attribute.create(namespace, name));
            return this;
        }

        public FilterRule filterAttr(String name) {
            return filterAttr(null, name);
        }

        @Override
        public int compareTo(FilterRule o) {
            return tagName.compareTo(o.tagName);
        }
    }

    public static class Attribute extends Pair<String, String> implements Comparable<Attribute> {

        private final String key;
        private final String value;
        private final int index;

        private Attribute(@Nullable String first, @Nullable String second) {
            this(first, second, null);
        }

        private Attribute(@Nullable String first, @Nullable String second, String value) {
            this(first, second, -1, value);
        }

        private Attribute(@Nullable String first, @Nullable String second, int index, String value) {
            super(first, second);
            this.key = buildKey(first, second);
            this.index = index;
            this.value = value;
        }

        private static Attribute create(@Nullable String first, @Nullable String second) {
            return new Attribute(first, second);
        }

        private static Attribute create(@Nullable String first, @Nullable String second, String value) {
            return new Attribute(first, second, value);
        }

        private static Attribute create(@Nullable String first, @Nullable String second, int index, String value) {
            return new Attribute(first, second, index, value);
        }

        private String buildKey(@Nullable String namespace, @NonNull String name) {
            return TextUtils.isEmpty(namespace) ? name : (namespace + ":" + name);
        }

        public String getValue() {
            return value;
        }

        public int getIndex() {
            return index;
        }

        public String getNamespace() {
            return first;
        }

        public String getName() {
            return second;
        }

        @Override
        public int compareTo(Attribute o) {
            return key.compareTo(o.key);
        }

        private boolean equals(@Nullable String first, @NonNull String second) {
            return key.equals(buildKey(first, second));
        }
    }

    public static class Tag {
        private final String tagName;
        private final List<Attribute> attrs = new ArrayList<>();
        private final List<String> texts = new ArrayList<>();

        public Tag(String tagName) {
            this.tagName = tagName;
        }

        public String getTagName() {
            return tagName;
        }

        public Attribute getAttr(int index) {
            final int N = attrs.size();
            for (int i = 0; i < N; i++) {
                Attribute attribute = attrs.get(i);
                if (attribute.index == index) {
                    return attribute;
                }
            }
            return null;
        }

        public String getAttrValue(String namespace, String name) {
            if (TextUtils.isEmpty(name)) {
                throw new IllegalArgumentException("This 'name' cannot be empty!");
            }
            final int N = attrs.size();
            for (int i = 0; i < N; i++) {
                Attribute attribute = attrs.get(i);
                if (attribute.equals(namespace, name)) {
                    return attribute.value;
                }
            }
            return null;
        }

        public String getAttrValue(String name) {
            return getAttrValue(null, name);
        }

        public List<Attribute> getAttrs() {
            return attrs;
        }

        public List<String> getTexts() {
            return texts;
        }
    }

}
